#include "iuApplication.hpp"
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <sys/wait.h>
#include <signal.h>
#include "iuCommand.hpp"
#include "iuCommandInterface.hpp"
#include "iuCommandIfconfig.hpp"
#include "iuCommandRoute.hpp"
#include "iuCommandArp.hpp"
#include "iuCommandPing.hpp"
#include "iuInterface.hpp"
#include "iuInterfaceFifo.hpp"
#include "iuInterfaceSocket.hpp"
#include "iuPacket.hpp"

#define SC_CHECK(expr, msg) \
{ \
   if ((expr) == -1) \
   { \
      perror(msg); \
      exit(EXIT_FAILURE); \
   } \
}

/**
 * @brief Initialise l'application
 */
iuApplication::iuApplication()
   : m_moduleIP(*this)
{
   m_commands[0] = new iuCommandInterface();
   m_commands[1] = new iuCommandIfconfig();
   m_commands[2] = new iuCommandRoute();
   m_commands[3] = new iuCommandArp();
   m_commands[4] = new iuCommandPing();

   m_interfaces = NULL;
   m_useFifo = false;
}

/**
 * @brief Detruit l'application
 */
iuApplication::~iuApplication()
{
   iuInterface *next;

   for (int i = 0; i < NUM_COMMANDS; i++)
      delete m_commands[i];

   while (m_interfaces)
   {
      next = m_interfaces->next;
      delete m_interfaces;
      m_interfaces = next;
   }
}

/**
 * @brief Lance l'application: un prompt va etre affiche, et les commandes
 * entrees vont etre executees. Les paquets recus sur les differentes interfaces
 * seront egalement traites
 * @param cmdFile le fichier de commandes a executer (peut-etre NULL)
 */
void iuApplication::run(const char *cmdFile, bool useFifo)
{
   pid_t p;

   m_useFifo = useFifo;
   SC_CHECK(((p = fork()) == -1), "fork");

   //Execute le shell dans le processus pere
   if (p != 0)
   {
      int status;

      //Execute les commandes du fichier
      if (cmdFile)
      {
         FILE *inFile;
         char line[256];

         if ((inFile = fopen(cmdFile, "rt")))
         {
            while (fgets(line, 256, inFile) != NULL)
            {
               for (int i = strlen(line) - 1; i >= 0; i--)
               {
                  if (line[i] != '\r' && line[i] != '\n')
                     break;
                  line[i] = '\0';
               }
               m_shell.executeCommand(line);
            }
            fclose(inFile);
         }
         else
            printf("[Application] Unable to open the command-file\n");
      }

      //Lance le shell
      m_shell.run();

      //Tue le processus fils, et attend qu'il soit mort
      kill(p, SIGKILL);
      waitpid(p, &status, 0);
   }

   //Traite les commandes entrees et les paquets recus sur les interfaces dans le processus fils
   else
   {
      struct pollfd pollFDs[256];
      int numPollFDs;
      char *cmd[MAX_ARGS];
      int numArgs;
      bool commandFound;

      pollFDs[0].fd = m_shell.getCommandFD();
      pollFDs[0].events = POLLIN;

      while (1)
      {
         //Detecte si une commande est entree ou si une interface a recu un paquet
         numPollFDs = 1;
         for (iuInterface *in = m_interfaces; in; in = in->next)
         {
            if (in->isActive())
            {
               pollFDs[numPollFDs].fd = in->getReceiveFD();
               pollFDs[numPollFDs].events = POLLIN;
               numPollFDs++;
            }
         }
         SC_CHECK(poll(pollFDs, numPollFDs, -1), "poll");

         for (int i = 0; i < numPollFDs; i++)
         {
            if (pollFDs[i].revents == POLLIN)
            {
               //On lit la commande passee et on l'execute
               if (i == 0)
               {
                  if ((numArgs = m_shell.getCommand(cmd)) <= 0)
                     continue;

                  commandFound = false;
                  for (int j = 0; j < NUM_COMMANDS; j++)
                  {
                     if (strcmp(m_commands[j]->getName(), cmd[0]) == 0)
                     {
                        m_commands[j]->execute(numArgs, cmd, *this);
                        commandFound = true;
                        break;
                     }
                  }

                  if (!commandFound)
                     printf("[Application] %s: command not found\n", cmd[0]);

                  m_shell.freeCommand(numArgs, cmd);
                  m_shell.resume();
               }

               //On lit le paquet recu sur l'interface
               else
               {
                  iuInterface *in;
                  iuPacket *packet;
                  uint16_t protocol;

                  //Recupere l'interface qui a recu un paquet
                  for (in = m_interfaces; in; in = in->next)
                  {
                     if (in->getReceiveFD() == pollFDs[i].fd)
                        break;
                  }

                  //Recupere le paquet recu et le transmet au bon module
                  if (in && (packet = in->receivePacket(&protocol)))
                  {
                     if (protocol == 0x800)
                     {
                        m_moduleIP.receivePacket(*(iuPacketIP *)(packet), *in);
                     }
                     else if (protocol == 0x806)
                     {
                        m_moduleARP.receivePacket(*(iuPacketARP *)(packet), *in);
                     }

                     delete packet;
                  }
               }
            }
         }
      }
   }
}

/**
 * @brief Recupere la premiere interface de l'application (les autres sont accessibles avec ->next)
 * @return Renvoie la premiere interface de l'application
 */
iuInterface *iuApplication::getInterfaces()
{
   return m_interfaces;
}

/**
 * @brief Recupere l'interface de l'application dont le numero est interfaceNum
 * @return Renvoie l'interface de l'application dont le numero est interfaceNum
 */
iuInterface *iuApplication::getInterfaceByNumber(unsigned char interfaceNum)
{
   iuInterface *in;

   for (in = m_interfaces; in; in = in->next)
   {
      if (in->getNumber() == interfaceNum)
         return in;
   }
   return NULL;
}

/**
 * @brief Ajoute une nouvelle interface (non configuree et non active)
 * @param port le port de l'interface a ouvrir
 * @return Renvoie la nouvelle interface creee, ou NULL en cas d'erreur
 */
iuInterface *iuApplication::addInterface(uint16_t port)
{
   iuInterface *interface, *last;

   //Cree une nouvelle interface et ouvre la connexion sur le port donne
   if (m_useFifo)
      interface = new iuInterfaceFifo();
   else
      interface = new iuInterfaceSocket();

   if (!interface->open(port))
   {
      delete interface;
      return NULL;
   }

   //Ajoute l'interface a la fin de la liste
   for (last = m_interfaces; last && last->next; last = last->next);
   if (last)
      last->next = interface;
   else
      m_interfaces = interface;

   return interface;
}

/**
 * @brief Supprime l'interface dont le numero est "interfaceNum"
 * @param "interfaceNum" le numero de l'interface a supprimer
 * @return Renvoie true si l'interface a ete supprimee, false si elle n'a pas pu etre trouvee
 */
bool iuApplication::delInterface(unsigned char interfaceNum)
{
   iuInterface *interface, *prev;

   for (interface = m_interfaces, prev = NULL; interface; prev = interface, interface = interface->next)
   {
      if (interface->getNumber() == interfaceNum)
      {
         if (prev)
            prev->next = interface->next;
         else
            m_interfaces = interface->next;

         //Supprime l'interface des regles de la table de routage et du cache ARP
         m_moduleIP.getRouteTable().removeRulesByInterface(interface);
         m_moduleARP.getARPTable().removeEntriesByInterface(interface);

         delete(interface);
         return true;
      }
   }
   return false;
}

/**
 * @brief Recupere le module IP de l'application
 * @return Renvoie le module IP de l'application
 */
iuModuleIP &iuApplication::getModuleIP()
{
   return m_moduleIP;
}

/**
 * @brief Recupere le module ARP
 * @return Renvoie le module ARP
 */
iuModuleARP &iuApplication::getModuleARP()
{
   return m_moduleARP;
}
